#!/usr/bin/env python3

from urllib.request import urlretrieve
import argparse
import json
import os
import re
import shutil, shlex
import subprocess
import sys
import tarfile
from pygit2 import Repository
import glob
from munch import Munch
import configparser
import platform

import pathlib
for d in pathlib.Path(__file__).resolve().parents:
  if os.path.exists(os.path.join(d, 'behave.ini')):
    ROOT = d
    break
os.chdir(ROOT)
# because behave doesn't think it's useful to be able to load local stuff... oy...
sys.path.insert(0, os.path.abspath('test/features/steps'))

CI = Munch(
  service = (os.environ.get('GITHUB_SHA') and 'github'),
  branch = (os.environ.get('GITHUB_REF', '').startswith('refs/heads/') and os.environ['GITHUB_REF'].split('/')[-1]) or '',
  event = os.environ.get('GITHUB_EVENT_NAME') or '',
  tag = (os.environ.get('GITHUB_REF', '').startswith('refs/tags/') and os.environ['GITHUB_REF'].split('/')[-1]) or '',
  message = (os.environ.get('GITHUB_SHA') and subprocess.check_output(['git', 'log', '--format=%B', '-n', '1', os.environ['GITHUB_SHA']], encoding='UTF-8').strip()) or ''
)
BRANCH = CI.branch if CI.branch != '' else Repository('.').head.name.split('/')[-1]
behave_ini = configparser.ConfigParser()
behave_ini.read(os.path.join(ROOT, 'behave.ini'))
sys.argv += shlex.split(behave_ini.get('branch', BRANCH, fallback=''))

class BooleanAction(argparse.Action):
  def __init__(self, option_strings, dest, nargs=None, **kwargs):
    super().__init__(option_strings, dest, nargs=0, **kwargs)

  def __call__(self, parser, namespace, values, option_string=None):
    setattr(namespace, self.dest, not option_string.startswith('--no'))
class ClientAction(argparse.Action):
  def __init__(self, option_strings, dest, nargs=None, **kwargs):
    super().__init__(option_strings, dest, nargs=0, **kwargs)

  def __call__(self, parser, namespace, values, option_string=None):
    print(type(input_string), type(values))
    if option_string == '--jurism' and type(values) == bool and values:
      setattr(namespace, self.dest, 'jurism')
    elif option_string == '--jurism' and type(values) == bool and not values:
      setattr(namespace, self.dest, 'zotero')
    elif option_string == '--client' and type(values) == str and values in ['zotero', 'jurism']:
      setattr(namespace, self.dest, values)
    else:
      raise ValueError(f'Unexpected argument {option_string} = {values}')
parser = argparse.ArgumentParser()
parser.add_argument('--start', dest='start', action=BooleanAction)
parser.add_argument('--build', '--no-build', dest='build', action=BooleanAction, default=(not CI.service))
parser.add_argument('--stop', '--no-stop', dest='stop', action=BooleanAction, default=(not CI.service))
parser.add_argument('--jurism', dest='client', action='store_const', const='jurism', default=os.environ.get('CLIENT', 'zotero'))
parser.add_argument('--client', dest='client', default=os.environ.get('CLIENT', 'zotero'))
parser.add_argument('--log-memory-every', dest='log_memory_every', type=int)
parser.add_argument('--beta', action='store_true', default=('#beta' in CI.message))
parser.add_argument('--keep', '--no-keep', dest='keep', action=BooleanAction, default=False)
parser.add_argument('--inspire-hep', '--no-inspire-hep', dest='inspireHEP', action=BooleanAction, default=True)
parser.add_argument('--worker', '--no-worker', dest='worker', action=BooleanAction, default=False)
parser.add_argument('--logging', '--no-logging', dest='logging', action=BooleanAction, default=False)
parser.add_argument('--caching', '--no-caching', dest='caching', action=BooleanAction, default=True)
parser.add_argument('--headless', '--no-headless', dest='headless', action=BooleanAction, default=platform.system() == 'Linux')
parser.add_argument('--this', action='store_true')
parser.add_argument('--test-this', action='store_true')
parser.add_argument('--slow', action='store_true',
  default = CI.branch == 'master' or
            '#slow' in CI.message or
            CI.event == 'schedule' or
            CI.tag != ''
)
parser.add_argument('--whopper', action='store_true', default = CI.event == 'schedule')
parser.add_argument('--test')
parser.add_argument('--import', dest='import_at_start')
parser.add_argument('--durations')
parser.add_argument('--bin')
parser.add_argument('--bins')
parser.add_argument('--tagged', action='store_true', default=CI.tag != '')
parser.add_argument('--nightly', action='store_true', default=(CI.event == 'schedule') or ('#nightly' in CI.message))
args, unknownargs = parser.parse_known_args()
sys.argv = sys.argv[:1] + unknownargs

if args.whopper:
  args.slow = True
  sys.argv += ['--tags', '@whopper']

if args.start:
  args.keep = True
  sys.argv += ['--tags', '@none']
if args.keep:
  args.headless = False

if args.client.endswith('-beta'):
  args.client = args.client.split('-')[0]
  args.beta = True

if args.build:
  process = subprocess.Popen(['npm', 'run', 'build'] + (['--beta'] if args.beta else []), stdout=subprocess.PIPE)
  while True:
    line = process.stdout.readline()
    print(line.decode('utf-8'), end='')
    if process.poll() is not None: break
  returncode = process.poll()
  if returncode != 0:
    print(f'Build exited with exit code {returncode}')
    sys.exit(returncode)

if args.this or args.test_this:
  assert re.match(r'^gh-[0-9]+$', BRANCH)
  if args.this: args.this = BRANCH.replace('gh-', '@')
  if args.test_this: args.test = BRANCH.replace('gh-', '')

if args.test or args.this or args.nightly or args.tagged or args.beta: args.slow = True

if args.client == 'jurism':
  print(f"********* JURISM NO LONGER SUPPORTED ***********")
  sys.exit(1)

sys.argv.extend(['--define', f"client={args.client}"])
sys.argv.extend(['--define', f'worker={str(args.worker).lower()}'])
sys.argv.extend(['--define', f'logging={str(args.logging).lower()}'])
sys.argv.extend(['--define', f'caching={str(args.caching).lower()}'])
sys.argv.extend(['--define', f'inspireHEP={str(args.inspireHEP).lower()}'])
if args.bin: sys.argv.extend(['--define', f"bin={args.bin}"])
if args.bins: sys.argv.extend(['--define', f"bins={args.bins}"])
sys.argv.extend(['--define', f'kill={str(not args.keep).lower()}'])
if args.stop: sys.argv.append('--stop')
if args.slow: sys.argv.extend(['--define', 'slow=true'])
if args.whopper: sys.argv.extend(['--define', 'whopper=true'])

if args.beta: sys.argv.extend(['--define', 'beta=true'])

if args.test: sys.argv.extend(['--define', f'test={args.test}'])
if args.headless: sys.argv.extend(['--define', f'headless=true'])
if args.durations: sys.argv.extend(['--define', f'durations={args.durations}'])
if args.import_at_start: sys.argv.extend(['--define', f'import={args.import_at_start}'])
if args.this: sys.argv.extend(['--tags', args.this ])
if args.log_memory_every: sys.argv.extend(['--define', f'log_memory_every={args.log_memory_every}'])

print('prepped with', args)
print('starting with', ' '.join(sys.argv))

# https://stackoverflow.com/questions/28829350/run-python-behave-from-python-instead-of-command-line
from behave.__main__ import Configuration, run_behave, Runner

# https://stackoverflow.com/questions/64855049/behave-run-features-in-random-order
class InstallLastRunner(Runner):
  def feature_locations(self):
    install = None
    locations = []
    for feature in super().feature_locations():
      if os.path.basename(feature.filename) == 'install.feature':
        install = feature
      else:
        locations.append(feature)
    if install: locations.append(install)
    return locations

def main():
  config = Configuration()
  sys.exit(run_behave(config, runner_class=InstallLastRunner))

if args.headless:
  from xvfbwrapper import Xvfb
  with Xvfb() as xvfb:
    main()
else:
  main()

